/// Text Overlay Application: DrawCC Class
/// This class contains all the necessary functions to receive, process
/// and render the content from a Close Captioning feed.


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace TextOverlay
{
    class DrawCC
    {
        public string InBuffer = "";
        string DrawBuffer = "";
        //int ShiftUpOffset = 0;
        long FramesBetweenCharacters = 1;
        long LastFrame = 0;
        long FramesSinceNewLine = 1000;
        private System.Drawing.Color TextColor = System.Drawing.Color.White;

        private Object CCLock = "lock";

        public DrawCC(Device device, int aFramesBetweenCharacters)
        {
            FramesBetweenCharacters = aFramesBetweenCharacters;
            d3d = device;
            mFont = new Font(device, 160, 0, FontWeight.Regular, 1, false, CharacterSet.Default, Precision.Default, FontQuality.Default, PitchAndFamily.DefaultPitch, "Arial");
            FontSprite = new Sprite(d3d);
            TextureSprite = new Sprite(d3d);
            fDescription = mFont.Description;
        }

        private Device d3d;
        private Sprite FontSprite;
        private Sprite TextureSprite;
        private Font mFont;
        private FontDescription fDescription;
        private int fontLife = 0;
        private void RecreateFont()
        {
            fontLife++;
            if (fontLife > 60)
            {
                mFont.Dispose();
                mFont = new Font(d3d, fDescription);
                fontLife = 0;
            }
        }
        /// <summary>
        /// Renders the Closed Captioning. Should be called on every frame while CC is on screen
        /// </summary>
        /// <param name="myFont">For drawing with</param>
        /// <param name="mySprite">may be null, or pass one if you have one</param>
        /// <param name="myColor">may be null, defaults to white</param>
        /// <param name="RowsToDraw">number of rows to draw (you probably want 3)</param>
        /// <param name="HeightPerRow">Pixels to render per row, baseline to baseline, not spacing between</param>
        /// <param name="X">X position of text for bottom row. (May not be correct bottom-left corner</param>
        /// <param name="Y">Y position of text for bottom row. (May not be correct bottom-left corner</param>
        public void Draw(Microsoft.DirectX.Direct3D.Font myFont, Texture BackTexture, System.Drawing.Color myColor,
            int RowsToDraw, int HeightPerRow, int X, int Y)
        {
            try
            {
                if (myColor != null) TextColor = myColor;
                int MeasureOffY = Y;
                int StringWidth = 0;
                if (FramesSinceNewLine * 5 < HeightPerRow) MeasureOffY = MeasureOffY + HeightPerRow - ((int)FramesSinceNewLine * 5);
                
                //if a new row, then bump down and scroll up, 1 px per frame
                string DrawString;
                FontSprite.Begin(SpriteFlags.AlphaBlend);
                TextureSprite.Begin(SpriteFlags.AlphaBlend);
                for (int row = 0; row <= RowsToDraw - 1; row++)
                {
                    DrawString = GetRow(row);
                    StringWidth = mFont.MeasureString(FontSprite, DrawString, DrawTextFormat.None, 0).Width;
                    if (StringWidth > 0)
                    {
                        TextureSprite.Transform = Matrix.Scaling(StringWidth + 20, HeightPerRow, 0) * Matrix.Translation(X, (MeasureOffY + 10) - (row * HeightPerRow), 0.0f);
                        TextureSprite.Draw(BackTexture, System.Drawing.Rectangle.Empty, Vector3.Empty, Vector3.Empty, System.Drawing.Color.White);
                        mFont.DrawText(FontSprite, DrawString, X, MeasureOffY - (row * HeightPerRow), TextColor);
                    }
                }
                TextureSprite.End();
                FontSprite.End();
                FramesSinceNewLine++;
                if (LastFrame % FramesBetweenCharacters == 0)
                {
                    if (IncrementCharacter())
                        FramesSinceNewLine = 0; //if we're on a new line, reset the upscroll
                }
                LastFrame++;
                PurgeOldRows(RowsToDraw + 2);
                RecreateFont();
            }
            catch
            {
                Track.LogInfo("Error Processing Close Caption (Draw)");
            }
        }

        public string GetRow(int RowFromBottom)
        {
            try
            {
                string[] sa = new string[] { };
                lock (CCLock)
                {
                    sa = DrawBuffer.Split(new string[] { "!0D" }, StringSplitOptions.None);
                }
                //if we have it, return the row in question
                if (RowFromBottom <= sa.Length - 1)
                    return ReduceFromHex(sa[sa.Length - 1 - RowFromBottom].ToString());
                return "";
            }
            catch
            {
                Track.LogInfo("Error Processing Close Caption (GetRow)");
                return "";
            }
        }

        /// <summary>
        /// resolve from the !HH format in which it is sent
        /// todo: deal with special characters
        /// </summary>
        /// <param name="HexString"></param>
        /// <returns></returns>
        private string s;
        private char c;
        public string ReduceFromHex(string HexString)
        {
            //return HexString;
            try
            {
                string retstring = "";
                for (int i = 0; i < HexString.Length; i += 3)
                {
                    s = HexString.Substring(i+ 1, 2);
                    c = (char)Convert.ToInt32(s, 16);

                    //special characters are sent as ( thecharacter | 0x80 )
                    if (c >= 128)
                        retstring += SpecialChars(c);
                    else if (c >= 32 && c < 128)
                        retstring += c.ToString();
                }
                return retstring ;
            }
            catch
            {
                lock (CCLock)
                {
                    InBuffer = "";
                    DrawBuffer = "";
                    Track.LogInfo("Error Processing Close Caption (ReduceFromHex)");
                }
                return "";
            }
        }

        private string SpecialChars(int c)
        {
            if (c == 176)
                return Convert.ToChar(174).ToString();
            if (c == 177)
                return Convert.ToChar(176).ToString();
            if (c == 178)
                return Convert.ToChar(189).ToString();
            if (c == 179)
                return Convert.ToChar(191).ToString();
            if (c == 180)
                return Convert.ToChar(8482).ToString();
            if (c == 181)
                return Convert.ToChar(162).ToString();
            if (c == 182)
                return Convert.ToChar(163).ToString();
            if (c == 183)
                return Convert.ToChar(9835).ToString();
            if (c == 184)
                return Convert.ToChar(224).ToString();
            if (c == 185)
                return " ";
            if (c == 186)
                return Convert.ToChar(232).ToString();
            if (c == 187)
                return Convert.ToChar(226).ToString();
            if (c == 188)
                return Convert.ToChar(234).ToString();
            if (c == 189)
                return Convert.ToChar(238).ToString();
            if (c == 190)
                return Convert.ToChar(244).ToString();
            if (c == 191)
                return Convert.ToChar(251).ToString();
            return ((char)c).ToString();
        }

        /// <summary>
        /// push one additional character into the draw buffer.
        /// 
        /// Return true if moving to new line
        /// </summary>
        public bool IncrementCharacter()
        {
            try
            {
                lock (CCLock)
                {
                    if (InBuffer.Length > 90)
                    {
                        InBuffer.Remove(0, 21);
                    }
                    if (InBuffer.Length > 2)
                    {
                        DrawBuffer += InBuffer.Substring(0, 3);
                        InBuffer = InBuffer.Remove(0, 3);
                        if (DrawBuffer.EndsWith("!0D"))
                            return true;
                    }
                }
                return false;
            }
            catch
            {
                Track.LogInfo("Error Processing Close Caption (Increment Character)");
                return false;
            }
        }

        public void PurgeOldRows(int KeepTheseRows)
        {
            try
            {
                lock (CCLock)
                {
                    string NewBuffer = "";
                    string[] sa = DrawBuffer.Split(new string[] { "!0D" }, StringSplitOptions.None);
                    if (sa.Length <= KeepTheseRows)
                        return;
                    for (int i = KeepTheseRows; i > 0; i--)
                        NewBuffer += "!0D" + sa[sa.Length - i];
                    if (NewBuffer.Length > 2) NewBuffer = NewBuffer.Remove(0, 3);
                    DrawBuffer = NewBuffer;
                }
            }
            catch
            {
                Track.LogInfo("Error Processing Close Caption (PurgeOldRows)");
            }
        }

        public void AppendCharacters(string chars, string source)
        {
            try
            {
                if (chars.Length > 0)
                {
                    lock (CCLock)
                    {
                        Console.WriteLine(source + chars + " -> " + ReduceFromHex(chars));
                        InBuffer += HexBugTrap(chars);
                        chars = null;
                    }
                    PurgeOldRows(4);
                }

                if (InBuffer.Length > 600)
                {
                    lock (CCLock)
                    {
                        InBuffer = InBuffer.Remove(0, (InBuffer.Length - 600));
                        while (InBuffer[0] != '!')
                            InBuffer = InBuffer.Remove(0, 1);
                    }
                }
            }
            catch
            {
                Track.LogInfo("Error Processing Close Caption (AppendCharacters)");
            }
        }

        private string HexBugTrap(string incoming)
        {
            while (true)
            {
                if (incoming.Contains("!0D!0D"))
                {
                    incoming = incoming.Replace("!0D!0D", "!0D");
                    //Console.WriteLine("-> BugTrap captured (Double BreakLine)");
                }
                else if (incoming.Contains("!20!20"))
                {
                    incoming = incoming.Replace("!20!20", "!20");
                   // Console.WriteLine("-> BugTrap captured (Double Space)");
                }
                else if (incoming.Contains("!50!75"))
                {
                    incoming = incoming.Replace("!50!75", "");
                   // Console.WriteLine("-> BugTrap captured (uP bug)");
                }
                else
                {
                    break;
                }
            }
            return incoming;
        }
    }
}
